// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//
#pragma once

#include <boost/algorithm/string/replace.hpp>
#include <fmt/format.h>
#include <ostream>

#include <pollux/common/base/exceptions.h>

namespace kumo::pollux::common {
    enum SubfieldKind {
        kAllSubscripts,
        kNestedField,
        kStringSubscript,
        kLongSubscript
    };

    // Contains field name separators to be used in Tokenizer.
    struct Separators {
        static const std::shared_ptr<Separators> &get() {
            static const std::shared_ptr<Separators> instance =
                    std::make_shared<Separators>();
            return instance;
        }

        bool isSeparator(char c) const {
            return (
                c == closeBracket || c == dot || c == openBracket || c == quote ||
                c == wildCard);
        }

        char backSlash = '\\';
        char closeBracket = ']';
        char dot = '.';
        char openBracket = '[';
        char quote = '\"';
        char wildCard = '*';
        char unicodeCaret = '^';
    };

    class Subfield {
    public:
        class PathElement {
        public:
            virtual ~PathElement() = default;

            virtual SubfieldKind kind() const = 0;

            virtual bool isSubscript() const = 0;

            virtual std::string toString() const = 0;

            virtual size_t hash() const = 0;

            virtual bool operator==(const PathElement &other) const = 0;

            virtual std::unique_ptr<PathElement> clone() = 0;
        };

        class AllSubscripts final : public PathElement {
        public:
            SubfieldKind kind() const override {
                return kAllSubscripts;
            }

            bool isSubscript() const override {
                return true;
            }

            std::string toString() const override {
                return "[*]";
            }

            size_t hash() const override {
                return 0;
            }

            bool operator==(const PathElement &other) const override {
                return other.kind() == kAllSubscripts;
            }

            std::unique_ptr<PathElement> clone() override {
                return std::make_unique<AllSubscripts>();
            }
        };

        class NestedField final : public PathElement {
        public:
            explicit NestedField(const std::string &name) : name_(name) {
                POLLUX_USER_CHECK(
                    !name.empty(), "NestedFields must have non-empty names.");
            }

            SubfieldKind kind() const override {
                return kNestedField;
            }

            const std::string &name() const {
                return name_;
            }

            bool operator==(const PathElement &other) const override {
                if (this == &other) {
                    return true;
                }
                return other.kind() == kNestedField &&
                       reinterpret_cast<const NestedField *>(&other)->name_ == name_;
            }

            size_t hash() const override {
                std::hash<std::string> hash;
                return hash(name_);
            }

            std::string toString() const override {
                return "." + name_;
            }

            bool isSubscript() const override {
                return false;
            }

            std::unique_ptr<PathElement> clone() override {
                return std::make_unique<NestedField>(name_);
            }

        private:
            const std::string name_;
        };

        class LongSubscript final : public PathElement {
        public:
            explicit LongSubscript(long index) : index_(index) {
            }

            SubfieldKind kind() const override {
                return kLongSubscript;
            }

            long index() const {
                return index_;
            }

            bool operator==(const PathElement &other) const override {
                if (this == &other) {
                    return true;
                }
                return other.kind() == kLongSubscript &&
                       reinterpret_cast<const LongSubscript *>(&other)->index_ == index_;
            }

            size_t hash() const override {
                std::hash<long> hash;
                return hash(index_);
            }

            std::string toString() const override {
                return "[" + std::to_string(index_) + "]";
            }

            bool isSubscript() const override {
                return true;
            }

            std::unique_ptr<PathElement> clone() override {
                return std::make_unique<LongSubscript>(index_);
            }

        private:
            const long index_;
        };

        class StringSubscript final : public PathElement {
        public:
            explicit StringSubscript(const std::string &index) : index_(index) {
            }

            SubfieldKind kind() const override {
                return kStringSubscript;
            }

            const std::string index() const {
                return index_;
            }

            bool operator==(const PathElement &other) const override {
                if (this == &other) {
                    return true;
                }
                return other.kind() == kStringSubscript &&
                       reinterpret_cast<const StringSubscript *>(&other)->index_ == index_;
            }

            size_t hash() const override {
                std::hash<std::string> hash;
                return hash(index_);
            }

            std::string toString() const override {
                return "[\"" + boost::replace_all_copy(index_, "\"", "\\\"") + "\"]";
            }

            bool isSubscript() const override {
                return true;
            }

            std::unique_ptr<PathElement> clone() override {
                return std::make_unique<StringSubscript>(index_);
            }

        private:
            const std::string index_;
        };

        // Separators: the customized separators to tokenize field name.
        explicit Subfield(
            const std::string &path,
            const std::shared_ptr<Separators> &separators = Separators::get());

        explicit Subfield(std::vector<std::unique_ptr<PathElement> > &&path);

        Subfield() = default;

        static std::unique_ptr<Subfield> create(const std::string &field) {
            std::vector<std::unique_ptr<PathElement> > path;
            path.push_back(std::make_unique<NestedField>(field));
            return std::make_unique<Subfield>(std::move(path));
        }

        const std::vector<std::unique_ptr<PathElement> > &path() const {
            return path_;
        }

        std::vector<std::unique_ptr<PathElement> > &path() {
            return path_;
        }

        bool isPrefix(const Subfield &other) const {
            if (path_.size() < other.path_.size()) {
                for (int i = 0; i < path_.size(); ++i) {
                    if (!(*path_[i].get() == *other.path_[i].get())) {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }

        std::string toString() const {
            if (!valid()) {
                return "";
            }
            std::ostringstream out;
            out << static_cast<const NestedField *>(path_[0].get())->name();
            for (int i = 1; i < path_.size(); i++) {
                out << path_[i]->toString();
            }
            return out.str();
        }

        bool operator==(const Subfield &other) const {
            if (this == &other) {
                return true;
            }

            if (path_.size() != other.path_.size()) {
                return false;
            }
            for (int i = 0; i < path_.size(); ++i) {
                if (!(*path_[i].get() == *other.path_[i].get())) {
                    return false;
                }
            }
            return true;
        }

        size_t hash() const {
            size_t result = 1;
            for (int i = 0; i < path_.size(); ++i) {
                result = result * 31 + path_[i]->hash();
            }
            return result;
        }

        bool valid() const {
            return !path_.empty() && path_[0]->kind() == kNestedField;
        }

        Subfield clone() const;

    private:
        std::vector<std::unique_ptr<PathElement> > path_;
    };

    inline std::ostream &operator<<(std::ostream &out, const Subfield &subfield) {
        return out << subfield.toString();
    }
} // namespace kumo::pollux::common

namespace std {
    template<>
    struct hash<::kumo::pollux::common::Subfield> {
        size_t operator()(const ::kumo::pollux::common::Subfield &subfield) const {
            return subfield.hash();
        }
    };
} // namespace std

template<>
struct fmt::formatter<::kumo::pollux::common::Subfield>
        : formatter<std::string> {
    auto format(const ::kumo::pollux::common::Subfield &s, format_context &ctx)
    const {
        return formatter<std::string>::format(s.toString(), ctx);
    }
};

template<>
struct fmt::formatter<::kumo::pollux::common::SubfieldKind>
        : formatter<int> {
    auto format(
        const ::kumo::pollux::common::SubfieldKind &s,
        format_context &ctx) const {
        return formatter<int>::format(static_cast<int>(s), ctx);
    }
};
